/**
  ETFDAQ Project
  \class TASIS3100
  \brief A wrapper class for SIS3100 Ctrller
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2024-08-11
  \date 2024-08-11 last modified

  \copyright Copyright (c) 2024 IMP-CAS with LGPLv3 LICENSE
*/

#include <sys/types.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <cstring>
#include "sis3100_vme_calls.h"
#include "TASIS3100.h"
#include "TAMsg.h"

#define er TAMsg::Error
#define wn TAMsg::Warn

TASIS3100::TASIS3100(const string &dev) : TAController(dev){
  fIRQCTL_ON.irq_mask = SIS3100_VME_IRQS; // 0xFE: all 1~7 irq levels are enabled
  fIRQCTL_ON.signal = fTargetSIG; // VME_IRQ will send a fTargetSIG signal to sis1100

  fIRQCTL_OFF.irq_mask = SIS3100_VME_IRQS; // 0xFE: all 1~7 irq levels are enabled
  fIRQCTL_OFF.signal = 0; // disable the IRQs
} // end ctor

TASIS3100::~TASIS3100(){}

void TASIS3100::Reset(){
  CtrlWriteD32(OFFSET_CTRL, 0x2); // set reset bit
  usleep(500000); // min. 200ms
  CtrlWriteD32(OFFSET_CTRL, 0x20000); // clear reset bit
  DisableIRQ(); // disable IRQ, or the following clearing will be in vain
  // clear all the outputs //
  for(int i = 3; i--;) Clear_LEMO_OUT(i + 1);
  for(int i = 4; i--;) Clear_FLAT_OUT(i + 1);
  // for(int i = 3; i--;) GeneratePulse_LEMO_OUT(i + 1);
  // for(int i = 4; i--;) GeneratePulse_FLAT_OUT(i + 1);
  // disable trigger at the beginning
  // Set_LEMO_OUT(2);
  // Set_LEMO_OUT(3);
} // end member function Reset

// show the IRQ register
void TASIS3100::PrintIRQStatus(){
  CtrlReadD32(OFFSET_IRQSC);
  printf("SIS IRQ Status: ");
  PrintD32InBinaryForm();
} // end member function IRQStatus

// mask: irq levels(1~7,3=1<<3), 0xFE: all 1~7 irq levels are enabled
void TASIS3100::EnableIRQ(int mask){
  // PrintIRQStatus(); // check it out -- DEBUG
  // turn on the total IRQ bit //
  // printf("Enabling the SIS3100 controller IRQ bits...\n"); // DEBUG
  CtrlWriteD32(OFFSET_IRQSC, 0x1); // the VME IRQ Enable bit -- one for all bit

  // turn on the subordinate IRQ bits //
  if(0xFE != mask) fIRQCTL_ON.irq_mask = mask & SIS3100_VME_IRQS;
  if(ioctl(fd, SIS1100_IRQ_CTL, &fIRQCTL_ON) < 0) perror("sis3100 enableIRQ");
  // PrintIRQStatus(); // check it out -- DEBUG
} // end member function EnableIRQ

void TASIS3100::DisableIRQ(){
  // PrintIRQStatus(); // check it out -- DEBUG
  // disable IRQ //
  // mask 0xFE: all 1~7 irq levels are disabled; signal=0 disables the IRQs
  if(ioctl(fd, SIS1100_IRQ_CTL, &fIRQCTL_OFF) < 0) perror("sis3100 disableIRQ");
  // PrintIRQStatus(); // check it out -- DEBUG
} // end member function DisableIRQ

// fetch and show interrupt status
void TASIS3100::GetIRQ() const{
  sis1100_irq_get irqget;
  irqget.irq_mask = SIS3100_VME_IRQS;
  if(ioctl(fd, SIS1100_IRQ_GET, &irqget) < 0) perror("sis3100 getIRQ");
  TAMsg::Info("TASIS3100", "GetIRQ: irqs=0x%08x level=%d vector=0x%02x, "
    "remote status=%d", irqget.irqs, irqget.level, irqget.vector, irqget.remote_status);
  // if(irqget.remote_status) TAMsg::Info("TASIS3100", "GetIRQ");
  if(!irqget.irqs) wn("TASIS3100", "GetIRQ: no irq bits set: unacceptable");
} // end member function GetIRQ

// set ch_out to forbit the trigger
void TASIS3100::DropDead(){
  // DisableIRQ(); // no need for this: disabling is automatic by sis3100
  // Set_LEMO_OUT(2);
  // Set_LEMO_OUT(3);
  if(fExtraDeadTime){
    usleep(fExtraDeadTime);
    // printf("extra dead time: %d\n", fExtraDeadTime); getchar(); // DEBUG
  } // end if
  // Clear_LEMO_OUT(3);
} // end member function DropDead

// marking the completion of reading data and prepare for new IRQs
void TASIS3100::Resurrect(){
  // the controller clears the IRQ enable bits corresponding to the irq levels
  // of the interrupters. So for the reading of next event, one has to recover
  // those IRQ bits
  EnableIRQ(); // recover the IRQ bits
  // Clear_LEMO_OUT(2);
  GeneratePulse_LEMO_OUT(1);
} // end member function Resurrect

void TASIS3100::GeneratePulse_LEMO_OUT(int ch){
  switch(ch){
    case 1: CtrlWriteD32(OFFSET_OPTINOUT, 0x10000000); break; // bit 28
    case 2: CtrlWriteD32(OFFSET_OPTINOUT, 0x20000000); break; // bit 29
    case 3: CtrlWriteD32(OFFSET_OPTINOUT, 0x40000000); break; // bit 30
    default: er("TASIS3100", "GeneratePulse_LEMO_OUT: ch=%d, "
      "but only 1, 2 and 3 are allowed", ch); break;
  } // end switch
} // end member function GeneratePulse_LEMO_OUT
void TASIS3100::Set_LEMO_OUT(int ch){
  switch(ch){
    case 1: CtrlWriteD32(OFFSET_OPTINOUT, 0x00000010); break; // bit 4
    case 2: CtrlWriteD32(OFFSET_OPTINOUT, 0x00000020); break; // bit 5
    case 3: CtrlWriteD32(OFFSET_OPTINOUT, 0x00000040); break; // bit 6
    default: er("TASIS3100", "Set_LEMO_OUT: ch=%d, "
      "but only 1, 2 and 3 are allowed", ch); break;
  } // end switch
} // end member function Set_LEMO_OUT
void TASIS3100::Clear_LEMO_OUT(int ch){
  switch(ch){
    case 1: CtrlWriteD32(OFFSET_OPTINOUT, 0x00100000); break; // bit 20
    case 2: CtrlWriteD32(OFFSET_OPTINOUT, 0x00200000); break; // bit 21
    case 3: CtrlWriteD32(OFFSET_OPTINOUT, 0x00400000); break; // bit 22
    default: er("TASIS3100", "Clear_LEMO_OUT: ch=%d, "
      "but only 1, 2 and 3 are allowed", ch); break;
  } // end switch
} // end member function Clear_LEMO_OUT
void TASIS3100::GeneratePulse_FLAT_OUT(int ch){
  switch(ch){
    case 1: CtrlWriteD32(OFFSET_OPTINOUT, 0x01000000); break; // bit 24
    case 2: CtrlWriteD32(OFFSET_OPTINOUT, 0x02000000); break; // bit 25
    case 3: CtrlWriteD32(OFFSET_OPTINOUT, 0x04000000); break; // bit 26
    case 4: CtrlWriteD32(OFFSET_OPTINOUT, 0x08000000); break; // bit 27
    default: er("TASIS3100", "GeneratePulse_FLAT_OUT: ch=%d, "
      "but only 1, 2, 3 and 4 are allowed", ch); break;
  } // end switch
} // end member function GeneratePulse_FLAT_OUT
void TASIS3100::Set_FLAT_OUT(int ch){
  switch(ch){
    case 1: CtrlWriteD32(OFFSET_OPTINOUT, 0x00000001); break; // bit 0
    case 2: CtrlWriteD32(OFFSET_OPTINOUT, 0x00000002); break; // bit 1
    case 3: CtrlWriteD32(OFFSET_OPTINOUT, 0x00000004); break; // bit 2
    case 4: CtrlWriteD32(OFFSET_OPTINOUT, 0x00000008); break; // bit 3
    default: er("TASIS3100", "Set_FLAT_OUT: ch=%d, "
      "but only 1, 2 and 3 are allowed", ch); break;
  } // end switch
} // end member function Set_FLAT_OUT
void TASIS3100::Clear_FLAT_OUT(int ch){
  switch(ch){
    case 1: CtrlWriteD32(OFFSET_OPTINOUT, 0x00010000); break; // bit 16
    case 2: CtrlWriteD32(OFFSET_OPTINOUT, 0x00020000); break; // bit 17
    case 3: CtrlWriteD32(OFFSET_OPTINOUT, 0x00040000); break; // bit 18
    case 4: CtrlWriteD32(OFFSET_OPTINOUT, 0x00080000); break; // bit 19
    default: er("TASIS3100", "Clear_FLAT_OUT: ch=%d, "
      "but only 1, 2 and 3 are allowed", ch); break;
  } // end switch
} // end member function Clear_FLAT_OUT


void TASIS3100::CtrlReadD32(int offset){
  const int res = s3100_control_read(fd, offset, &fD32);
  if(res) er("TASIS3100", "CtrlReadD32: error code = 0x%08x", res);
} // end member function CtrlReadD32
void TASIS3100::CtrlWriteD32(int offset, uint32_t data){
  const int res = s3100_control_write(fd, offset, data);
  if(res) er("TASIS3100", "CtrlWriteD32: error code = 0x%08x", res);
} // end member function CtrlWriteD32

// read functions //
uint32_t TASIS3100::A32D32Read(uint32_t addr){
  const int res = vme_A32D32_read(fd, addr, &fD32);
  if(res) wn("TASIS3100", "A32D32Read: error code = 0x%08x", res);
  return fD32;
} // end member function A32D32Read
uint16_t TASIS3100::A32D16Read(uint32_t addr){
  const int res = vme_A32D16_read(fd, addr, &fD16);
  if(res) wn("TASIS3100", "A32D16Read: error code = 0x%08x", res);
  return fD16;
} // end member function A32D16Read
// write functions
void TASIS3100::A32D32Write(uint32_t addr, uint32_t data){
  const int res = vme_A32D32_write(fd, addr, data);
  if(res) wn("TASIS3100", "A32D32Write: error code = 0x%08x", res);
} // end member function A32D32Write
void TASIS3100::A32D16Write(uint32_t addr, uint16_t data){
  const int res = vme_A32D16_write(fd, addr, data);
  if(res) wn("TASIS3100", "A32D16Write: error code = 0x%08x", res);
} // end member function A32D16Write

// \retval nof words read \param nreq: requested nof 32-bit words to be read
int TASIS3100::A32BLT32Read(uint32_t addr, uint32_t *data, uint32_t nreq){
  uint32_t nget;
  const int res = vme_A32BLT32_read(fd, addr, data, nreq, &nget);
  if(res) wn("TASIS3100", "A32BLT32Read: error code: 0x%08x", res);
  return nget;
} // end member function A32BLT32Read

// \retval nof words read \param nreq: requested nof **32-bit** words to be read
int TASIS3100::A32MBLT64Read(uint32_t addr, uint32_t *data, uint32_t nreq){
  uint32_t nget;
  const int res = vme_A32MBLT64_read(fd, addr, data, nreq, &nget);
  if(res) wn("TASIS3100", "A32MBLT64Read: error code: 0x%08x", res);
  return nget;
} // end member function A32MBLT64Read
